#include "syntax.h"

/* compare string from start-index to the str's end-index, and return the end index.
   @param str: the target string to full match
   @param cs: cs is a string too, and strlen(cs) >= strlen(str) */
static mx_size_t strcmpcs (mx_string_pt str, mx_string_pt cs)
{
    mx_string_pt pc;
    mx_size_t i;
    for (pc = str, i = 0; *pc != '\0'; pc = &(str)[++i])
    {
        {
            if (*pc != cs[i])
            {
                return -1;
            }
        }
    }
    return i;
}

/**
 * analysis a string and explain the cells in it
 * @param cells: the syntax cells array
 * @param clen: the length og cells
 * @param str: target string to explain
 * @param resbuf: a buffer to save the explained result, and it return by this function
 * @param bufsize: the size of `resbuf`
 * @return: `resbuf`. or mx_null means that syntax error
 */
mx_string_pt mx_syntax_cell_parser (mx_syntax_cell_pt cells, mx_usize_t clen, mx_string_pt str,
                                    mx_string_pt resbuf, mx_usize_t bufsize)
{
    char nresbuf[bufsize];  // the next resbuf for recursive call
    mx_usize_t bufwp = 0;   //the write pointer of resbuf

    mx_usize_t buf_lastidx = bufsize - 1;
    resbuf[buf_lastidx] = '\0';

    mx_str_foreachi(str, pc, i, {
        mx_bool_t nomatch = mx_true;
        /* match the start-stop symbol */
        for (mx_syntax_cell_pt cell = cells;
             cell < &cells[clen]; cell++)
        {
            mx_size_t sse;  // sse: "`string", start-symbol's end index (`pc` offset)
            mx_size_t ess;  // ess: "string`", end-symbol's start index (`pc` offset)
            /* match start symbol */
            sse = strcmpcs(cell->start, pc);
            if (sse == -1)
                continue;

            /* match stop symbol, return mx_null if not match */
            mx_bool_t byo = mx_false;   // break by other symbol
            if (cell->type == MX_SYNTAX_TYPE_SYMMETRIC)
            {
                /* symmetric nesting, like `" " " `" */
                ess = mx_strsib(&pc[sse], cell->stop);
            }
            else
            {
                ess = -1;
                if (cell->sbreak)
                {
                    /* support break by other symbol, like $var"string" */
                    mx_size_t ii = 0;
                    mx_str_foreach(&pc[sse], ipc, {
                        /* break by space or \0 */
                        if (*ipc == ' ')
                        {
                            byo = mx_true;
                            ess = ii;
                            break;
                        }
                        /* match other symbols */
                        mx_bool_t match = mx_false;
                        for (mx_syntax_cell_pt icell = cells; icell < &cells[clen]; icell++)
                        {
                            if (strcmpcs(icell->start, ipc) != -1 ||
                                strcmpcs(icell->stop, ipc) != -1)
                            {
                                /* mark that break by other symbol */
                                if (icell != cell)
                                    byo = mx_true;
                                ess = ii;
                                match = mx_true;
                                break;
                            }
                        }
                        if (match)
                            break;
                        ii ++;
                    });
                    /* not be breaked by other symbol, it must be "$var" */
                    if (ess == -1)
                    {
                        byo = mx_true;
                        ess = ii;
                    }
                }
                else    // MX_SYNTAX_TYPE_SEQUENTIAL
                {
                    /* not support break by other symbol, but support sequential nesting, like `$( $() `) $() */
                    mx_size_t nc = 0;   // nest count
                    mx_str_foreachi(&pc[sse], ipc, ii, {
                        if (strcmpcs(cell->stop, ipc) != -1)
                        {
                            if (nc == 0)
                            {
                                ess = ii;
                                break;
                            }
                            else
                                nc --;
                        }
                        else if (strcmpcs(cell->start, ipc) != -1)
                            nc ++;
                    });
                }
            }
            if (ess != -1)
            {
                /* sizeof(start) + sizeof(end) */
                ess = ess + sse;
                if (byo)
                {
                    /* the outer large loop skips this context,
                       $var`"string" -> -1 -> $va`r"string" */
                    i = i + ess - 1;
                }
                else
                {
                    /* the outer large loop skips this context,
                       "context`"string , i will +1 in the end of outer loop -> "context"`string */
                    i = i + mx_strlen(cell->stop) + ess - 1;
                }
            }
            else
            {
                if (cell == &cells[clen - 1])
                    return mx_null; // syntax error, all cells are searched but not find
                else
                    continue;
            }
            
            /* mark matched symbol, it's not a normal char */
            nomatch = mx_false;

            /* the stop-symbol matched and processing,
               if the stop-symbol match fails, it will not run here */
            mx_string_pt ostr;
            char cbbuf[bufsize];
            char temp = pc[ess];
            pc[ess] = '\0';     // mark the \0 ( "context" -> "context\0)
            if (cell->snest)    // support syntax nesting, like $($())
            {
                /* parse nested context ( "context" --pass--> context\0 ) */
                ostr = mx_syntax_cell_parser(cells, clen, &pc[sse], nresbuf, bufsize);
                if (ostr != mx_null)
                {
                    /* callback, the ostr is `nresbuf` */
                    ostr = cell->callback(cell, ostr, cbbuf, bufsize);
                }
                else
                    return mx_null; // syntax error
            }
            else
            {
                /* processing context ( "context" --pass--> context\0 ) */
                ostr = cell->callback(cell, &pc[sse], cbbuf, bufsize);
                if (ostr == mx_null)
                    return mx_null; // some error occur
            }

            /* copy output string to `resbuf` */
            if (ostr != mx_null)
            {
                mx_str_foreach(ostr, ipc, {
                    if (bufwp < (buf_lastidx))  // resbuf[bufsize - 1] reserve for '\0'
                        resbuf[bufwp++] = *ipc;
                    else
                        break;
                })
                /* recover \0 to temp, put this step after `copy output string to resbuf` prevent that
                   return `&pc[sse]` from callback */
                pc[ess] = temp;
                break;  // stop match residual symbol
            }
            else
                return mx_null; // error occur by callback
            /* recover \0 to temp, put this step after `copy output string to resbuf` prevent that
               return `&pc[sse]` from callback */
            pc[ess] = temp;
        }

        /* normal char */
        if (nomatch)
        {
            if (bufwp < (buf_lastidx))
                resbuf[bufwp++] = *pc;
            else
                break;
        }
    });
    resbuf[bufwp] = '\0';
    return resbuf;
}
